/*
 * \brief  DDE Linux 2.6 PCI backend
 * \author Björn Döbel
 * \author Christian Helmuth
 * \date   2008-11-11
 */

#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/list.h>

#include "pci.h"  /* drivers/pci/pci.h */

#include "local.h"


DECLARE_INITVAR(dde_linux26_pci);

/* PCI device descriptor */
typedef struct dde_linux26_pci_dev {
	struct list_head        next;         /* chain info */
	struct dde_kit_pci_dev *dde_kit_dev;  /* corresponding DDE kit descriptor */
	struct pci_dev         *linux_dev;    /* Linux descriptor */
} dde_linux26_pci_dev_t;


/**************
 ** PCI data **
 **************/

/* List of Linux to DDE kit PCI device mappings */
static LIST_HEAD(pcidev_mappings);


/* PCI bus */
static struct pci_bus *pci_bus = NULL;

static int dde_linux26_pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);
static int dde_linux26_pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val);


/* PCI operations for our virtual PCI bus */
static struct pci_ops dde_linux26_pcibus_ops = {
	.read = dde_linux26_pci_read,
	.write = dde_linux26_pci_write,
};


/************************************************
 ** Mapping DDE Linux 2.6 data to DDE kit data **
 ************************************************/

/**
 * Add DDE kit device to our list of known devices.
 *
 * Allocate Linux device for a DDE kit PCI device and fill in necessary values.
 */
static void _add_dde_kit_device(struct dde_kit_pci_dev *dev)
{
	dde_linux26_pci_dev_t *pci_dev = kmalloc(sizeof(dde_linux26_pci_dev_t), GFP_KERNEL);
	dde_kit_assert(pci_dev);

	int devfn = dde_kit_pci_get_devfn(dev);

	pci_dev->dde_kit_dev = dev;
	pci_dev->linux_dev = pci_scan_single_device(pci_bus, devfn);
	/* Since we know, that there must be a device with this function, we do not
	 * tolerate initialization failure. */
	dde_kit_assert(pci_dev->linux_dev);

	DEBUG_MSG("Detected device: %04x:%04x",
	          pci_dev->linux_dev->vendor,
	          pci_dev->linux_dev->device);
	if (!pci_dev->linux_dev)  {
		kfree(pci_dev);
		return;
	}

	list_add_tail(&pci_dev->next, &pcidev_mappings);
}


/**
 * Map Linux pci_dev to a DDE kit PCI device.
 *
 * \param dev      Linux device to map.
 *
 * \return dde_kit_dev if available
 * \return NULL        if no corresponding DDE kit device exists.
 */
static struct dde_kit_pci_dev *_linux_to_dde_kit(struct pci_dev *dev)
{
	struct list_head *p, *h;
	struct dde_kit_pci_dev *ret = NULL;

	h = &pcidev_mappings;
	list_for_each(p, h) {
		dde_linux26_pci_dev_t *d = list_entry(p, dde_linux26_pci_dev_t, next);
		if (d->linux_dev == dev) {
			ret = d->dde_kit_dev;
			break;
		}
	}

	dde_kit_assert(ret);
	return ret;
}


/*********************************
 ** Read/write PCI config space **
 *********************************/

static int dde_linux26_pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val)
{
	switch(size) {
		case 1:
			return dde_kit_pci_readb(PCI_SLOT(devfn), PCI_FUNC(devfn), where, (u8 *)val);
			break;
		case 2:
			return dde_kit_pci_readw(PCI_SLOT(devfn), PCI_FUNC(devfn), where, (u16 *)val);
			break;
		case 4:
			return dde_kit_pci_readl(PCI_SLOT(devfn), PCI_FUNC(devfn), where, val);
			break;
	}

	return 0;
}


static int dde_linux26_pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val)
{
	switch(size)
	{
		case 1:
			return dde_kit_pci_writeb(PCI_SLOT(devfn), PCI_FUNC(devfn), where, val);
			break;
		case 2:
			return dde_kit_pci_writew(PCI_SLOT(devfn), PCI_FUNC(devfn), where, val);
			break;
		case 4:
			return dde_kit_pci_writel(PCI_SLOT(devfn), PCI_FUNC(devfn), where, val);
			break;
	}

	return 0;
}


int pcibios_enable_resources(struct pci_dev *dev, int mask)
{
	u16 cmd, old_cmd;
	int idx;
	struct resource *r;

	pci_read_config_word(dev, PCI_COMMAND, &cmd);
	old_cmd = cmd;
	for (idx = 0; idx < PCI_NUM_RESOURCES; idx++) {
		/* Only set up the requested stuff */
		if (!(mask & (1 << idx)))
			continue;

		r = &dev->resource[idx];
		if (!(r->flags & (IORESOURCE_IO | IORESOURCE_MEM)))
			continue;
		if ((idx == PCI_ROM_RESOURCE) &&
				(!(r->flags & IORESOURCE_ROM_ENABLE)))
			continue;
		if (!r->start && r->end) {
			printk(KERN_ERR "PCI: Device %s not available "
				"because of resource collisions\n",
				pci_name(dev));
			return -EINVAL;
		}
		if (r->flags & IORESOURCE_IO)
			cmd |= PCI_COMMAND_IO;
		if (r->flags & IORESOURCE_MEM)
			cmd |= PCI_COMMAND_MEMORY;
	}
	if (cmd != old_cmd) {
		printk("PCI: Enabling device %s (%04x -> %04x)\n",
			pci_name(dev), old_cmd, cmd);
		pci_write_config_word(dev, PCI_COMMAND, cmd);
	}
	return 0;
}


int pcibios_enable_device(struct pci_dev *dev, int mask)
{
	int err;

	if ((err = pcibios_enable_resources(dev, mask)) < 0)
		return err;

	/*
	 * Linux enable IRQs here.
	 */

	return 0;
}


void pcibios_set_master(struct pci_dev *dev)
{
	/*
	 * Linux does some workarounds for broken BIOSes here.
	 */
}


void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev)
{
	/*
	 * Linux does some fixups for broken devices here. Most of this must be
	 * done in the PCI driver, but there are some normal device fixups too.
	 */
}


int pci_create_sysfs_dev_files(struct pci_dev *pdev)
{
	return 0;
}


/********************
 ** Initialization **
 ********************/

extern int pci_init(void);
extern int pci_driver_init(void);

/** Initialize PCI subsystem */
int dde_linux26_init_pci(void)
{
	struct dde_kit_pci_dev *last = NULL;
	struct dde_kit_pci_dev *next = NULL;
	int err;

	dde_kit_pci_init();

	pci_bus = pci_create_bus(NULL, 0, &dde_linux26_pcibus_ops, NULL);
	dde_kit_assert(pci_bus);

	/*
	 * Now that DDE kit has initialized its PCI devices, we can parse the bus
	 * again to add Linux PCI structs.
	 *
	 * We need to do this before calling any Linux PCI functions since these
	 * want to have the pci_devices list & co. populated.
	 */
	while (1) {

		next = dde_kit_pci_next_device(last);
		if (!next) break;

		last = next;
		_add_dde_kit_device(next);
	}
	pci_bus_add_devices(pci_bus);

	INITIALIZE_INITVAR(dde_linux26_pci);

	return 0;
}

subsys_initcall(dde_linux26_init_pci);
